use std::{
	collections::HashMap,
	path::{Path, PathBuf},
};

pub mod reader;

pub mod config;

pub mod function;
pub mod value;

pub mod access;
pub mod attr;
pub mod fmt;
pub use fmt::Class;
pub mod constant;
pub mod field;
pub mod method;
pub mod module;

pub use access::Access;
pub use attr::Attribute;
pub use config::Config;
pub use constant::Constant;
pub use field::Field;
pub use function::Function;
pub use method::Method;
pub use module::Module;
pub use value::Value;

fn cli() -> clap::Command {
	use clap::{arg, Command};
	Command::new("jnim")
		.about("jni bind generator for jnim")
		.subcommand_required(true)
		.arg_required_else_help(true)
		.subcommand(
			Command::new("check")
				.about("check,no output")
				.arg(arg!(<CONFIG> "The config file"))
				.arg_required_else_help(true),
		)
		.subcommand(
			Command::new("build")
				.about("generae bind")
				.arg(arg!(<CONFIG> "The config file"))
				.arg_required_else_help(true),
		)
}

fn main() {
	let matches = cli().get_matches();

	match matches.subcommand() {
		Some(("check", sub_matches)) => {
			let config = sub_matches.get_one::<String>("CONFIG").expect("required");
			println!("check {}", sub_matches.get_one::<String>("CONFIG").expect("required"));
			run(config, true);
		}
		Some(("build", sub_matches)) => {
			let config = sub_matches.get_one::<String>("CONFIG").expect("required");
			println!("build {}", sub_matches.get_one::<String>("CONFIG").expect("required"));
			run(config, false);
		}
		_ => unreachable!(),
	}
}
fn run(cfg_file: &String, check: bool) {
	let bytes = std::fs::read_to_string(cfg_file).unwrap();
	let mut config: Config = serde_json::from_str(&bytes).unwrap();
	config.init(cfg_file);
	let (tx, rx) = std::sync::mpsc::channel::<String>();

	for (f, _) in config.needs.iter() {
		tx.send(f.clone()).ok();
	}

	let mut files: HashMap<String, fmt::Class> = Default::default();
	while let Ok(path) = rx.try_recv() {
		if config.name(&path).is_lang() {
			continue;
		}
		let bytes = config.load_class(&format!("{path}.class")).unwrap();
		let Some(class) = fmt::Class::parse(&bytes) else {
			println!("failed: {}", path);
			continue;
		};

		tx.send(class.super_.clone()).ok();
		for i in class.faces.iter() {
			tx.send(i.clone()).ok();
		}

		config.loads.insert(path.as_str().to_string(), class.access);
		files.insert(path.clone(), class);
	}

	let mut new_interface: HashMap<String, Vec<String>> = Default::default();
	for (path, _class) in files.iter() {
		let mut out = vec![];
		find_interface_tree(&mut out, path, &files);
		new_interface.insert(path.clone(), out);
	}
	for (path, ni) in new_interface {
		if let Some(class) = files.get_mut(&path) {
			class.faces = ni;
		}
	}

	let out_dir: PathBuf = (&config.out_dir).into();
	let mut mods = Module::default();
	for (path, class) in files.iter() {
		let out_path = out_dir.join(&path).with_extension("rs");
		let out_path = out_path.display().to_string().to_ascii_lowercase().replace("$", "");
		let out_path: PathBuf = out_path.into();
		if !check {
			std::fs::create_dir_all(&out_path.parent().unwrap()).unwrap();
		}
		if class.access.is_interface() {
			let options = fmt::interface::Options { cfg: &config };
			let output = fmt::FmtInterface::new(&class, options).unwrap();
			if !check {
				fmt_output(&out_path, &format!("{output}")).unwrap();
			}
			mods.push(&mut path.to_ascii_lowercase().split("/").map(|v| v.to_string()).collect());
		} else {
			let options = fmt::class::Options { cfg: &config };
			let output = fmt::FmtClass::new(&class, options).unwrap();
			if !check {
				fmt_output(&out_path, &format!("{output}")).unwrap();
			}
			mods.push(&mut path.to_ascii_lowercase().split("/").map(|v| v.to_string()).collect());
		}
	}
	if !check {
		let gen_path = out_dir.join("mod.rs");
		fmt_output(&gen_path, &format!("{mods}")).unwrap();
	}
}

fn fmt_output(path: &Path, src: &str) -> Option<()> {
	use std::io::Write;
	use std::process::*;
	let mut child = Command::new("rustfmt").stdin(Stdio::piped()).stdout(Stdio::piped()).spawn().ok()?;
	child.stdin.as_mut()?.write_all(src.as_bytes()).ok()?;
	let output = child.wait_with_output().ok()?;
	if output.status.success() {
		let raw = String::from_utf8(output.stdout).ok()?;
		std::fs::write(path, format!("#[rustfmt::skip]\n{raw}")).ok()?;
		Some(())
	} else {
		None
	}
}

fn find_interface_tree(out: &mut Vec<String>, cur: &String, files: &HashMap<String, fmt::Class>) {
	if let Some(class) = files.get(cur) {
		for i in class.faces.iter() {
			out.push(i.clone());
			find_interface_tree(out, i, files)
		}
	}
}
